#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/fs.h>

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/select.h>

#include <libaio.h>

#include "bmap.h"
#include "bmap_seeksim.h"


void bmap_analyze(struct bmap *bm)
{
	uint64_t blk_cnt=0, frag_cnt=0, frag_min=1, frag_max=1;
	unsigned long *table, i, max_entries;
	struct bmap_extent *e;
	assert(bm!=NULL);
	for(e=bm->head;e!=NULL; e=e->next){
		blk_cnt += e->length;
		frag_cnt++;
		if(e == bm->head){
			frag_min = e->length;
			frag_max = e->length;
		}else{
			if(e->length < frag_min){
				frag_min = e->length;
			}
			if(e->length > frag_max){
				frag_max = e->length;
			}
		}
	}
	printf("blk_cnt: %llu\n", (long long unsigned)blk_cnt);
	printf("frag_cnt: %llu\n", (long long unsigned)frag_cnt);
	printf("frag_min: %llu\n", (long long unsigned)frag_min);
	printf("frag_max: %llu\n", (long long unsigned)frag_max);
	printf("frag_avg: %llu\n", (long long unsigned)blk_cnt/frag_cnt);
	max_entries = (frag_max - frag_min) + 1;
	table = (unsigned long *)malloc(sizeof(unsigned long)*max_entries);
	for(i=0; i<max_entries; i++) {
		table[i]=0;
	}
	for(e=bm->head;e!=NULL; e=e->next){
		table[e->length-frag_min]++;
	}
	for(i=0; i<max_entries; i++) {
		if(table[i]>0){
			printf("size: %llu -> %lf\n", (long long unsigned)(frag_min+i), table[i]/(double)frag_cnt);
		}
	}
	free(table);
}


//****************************************************************************************

//**************************************************************************
struct dist_node {
	long long key;
	long long count;
	struct dist_node *next;
};
struct dist {
	int step;
	long long min_key, max_key;
	long long total_count;
	struct dist_node *head;
};

struct dist * dist_create(int step)
{
	assert(step > 0);
	struct dist *d;
	d = (struct dist *)malloc(sizeof(struct dist));
	assert(!!d);
	d->head = NULL;
	d->step = step;
	d->max_key = 0;
	d->total_count = 0;
	return d;
}
void dist_destroy(struct dist *d)
{
	assert(!!d);
	struct dist_node *dn;
	while(!!(dn=d->head)){
		d->head = dn->next;
		free(dn);
	}
	free(d);
}

void dist_insert(struct dist *d, long long key0, int count)
{
	assert(!!d);
	long long key = ((key0<0)?(-key0):(key0)) / d->step;
	d->total_count += count;
	struct dist_node **p, *dn;
	for(p = &(d->head); !!(*p); p=&((*p)->next)){
		if((*p)->key < key) {
			continue;
		}
		if((*p)->key == key){
			(*p)->count += count;
			return;
		}
		break;
	}
	dn = (struct dist_node *)malloc(sizeof(struct dist_node));
	assert(!!dn);
	dn->key = key;
	dn->count = count;
	dn->next = *p;

	*p = dn;

	if(!dn->next){
		d->max_key = key;
	}
	if(dn == d->head){
		d->min_key = key;
	}
	
}

void dist_sort(struct dist *d)
{
	assert(!!d);
	struct dist_node *dn, **p, *desc=NULL;
	while(!!(dn=d->head)){
		d->head = dn->next;
		for(p=&desc; !!(*p)&&((*p)->count > dn->count); p=&((*p)->next));
		dn->next = *p;
		*p = dn;
	}
	d->head = desc;
}
char * dist_string(struct dist *d, char *buffer)
{
	assert(!!d);
	assert(!!buffer);
	long long i, cnt=0;
	struct dist_node *dn;
	for(i=d->min_key, dn = d->head; !!dn; dn=dn->next){
		while(i < dn->key){
			cnt += sprintf(buffer+cnt, "%d\t", 0);
			i++;
		}
		//printf("%lu(%lu)\t", dn->key, dn->count);
		cnt += sprintf(buffer+cnt, "%lld\t", dn->count);
		i++;
	}
	buffer[cnt]=0;
	return buffer;
}
int percentage_format=0;
int sequential_format=0;
int step_format = 1024;

static void dist_report_contiguous(struct dist *d, FILE *fp)
{
	assert(!!d);
	assert(!!fp);
	long long i;
	struct dist_node *dn;
	for(i=1, dn = d->head; !!dn; dn=dn->next){
		while(i < dn->key){
			fprintf(fp, "%d\t", 0);
			i++;
		}
		//printf("%lu(%lu)\t", dn->key, dn->count);
		if(!percentage_format){
			fprintf(fp, "%lld\t", dn->count);
		}else{
			fprintf(fp, "%lf\t", (100.0 * dn->count)/d->total_count);
		}
		i++;
	}
}
static void dist_report_discontiguous(struct dist *d, FILE *fp)
{
	assert(!!d);
	assert(!!fp);
	dist_sort(d);
	struct dist_node *dn;
	for(dn = d->head; !!dn; dn=dn->next){
		if(!percentage_format){
			fprintf(fp, "%lld(%lld)\t", dn->key, dn->count);
		}else{
			fprintf(fp, "%lld(%lf)\t", dn->key, (100.0 * dn->count)/d->total_count);
		}
	}
}

void dist_report(struct dist *d, FILE *fp)
{
	assert(!!d);
	assert(!!fp);
	if(!sequential_format){
		dist_report_discontiguous(d,fp);
	}else{
		dist_report_contiguous(d,fp);
	}
}

//*******************************************************************************************
static const char *fragsize_taskname;
static struct dist *fragsize_dist;


static void fragsize_start(const char *name)
{
	assert(!!name);
	fragsize_taskname = name;
	fragsize_dist = dist_create(step_format);
	
}
static void fragsize_bmap(struct bmap *bm)
{
	assert(!!bm);
	struct bmap_extent *e;
	for(e=bm->head; !!e; e=e->next){
		dist_insert(fragsize_dist, e->length, 1);
	}
	
}
static void fragsize_report(FILE *fp)
{
	assert(!!fp);
	fprintf(fp, "fragsize(%d)\t%s\t", step_format, fragsize_taskname);
	dist_report(fragsize_dist, fp);
	fprintf(fp, "\n");
	dist_destroy(fragsize_dist);
}
// *******************************************************************************************
static const char *fraginterval_taskname;
static struct dist *fraginterval_dist;

static void fraginterval_start(const char *name)
{
	assert(!!name);
	fraginterval_taskname = name;
	fraginterval_dist = dist_create(step_format);
	
}
static void fraginterval_bmap(struct bmap *bm)
{
	assert(!!bm);
	struct bmap_extent *e, *last;
	for(e=bm->head; !!e; e=e->next){
		if(e!=bm->head){
			dist_insert(fraginterval_dist, e->offset-(last->offset+last->length), 1);
		}
		last = e;
	}
	
}
static void fraginterval_report(FILE *fp)
{
	assert(!!fp);
	fprintf(fp, "fraginterval(%d)\t%s\t", step_format, fraginterval_taskname);
	dist_report(fraginterval_dist, fp);
	fprintf(fp, "\n");
	dist_destroy(fraginterval_dist);
}

//******************************************************************************************
static const char *base_taskname;
static uint64_t base_fragcnt;
static uint64_t base_filecnt;
static uint64_t base_blkcnt;
static double base_seeksim;
static void base_start(const char *name)
{
	assert(!!name);
	base_taskname = name;
	base_fragcnt = 0;
	base_filecnt = 0;
	base_blkcnt = 0;
	base_seeksim = 0;
}

static void base_bmap(struct bmap *bm)
{
	assert(!!bm);
	struct bmap_extent *e;
	for(e=bm->head; !!e; e=e->next){
		base_fragcnt++;
	}
	if(bm->blkcnt > 0){
		base_fragcnt--;
		base_blkcnt += bm->blkcnt;
		base_filecnt++;
		base_seeksim += bmap_seeksim(bm);
	}
}

static void base_report(FILE *fp)
{
	assert(!!fp);
	fprintf(fp, "base\t%s\t%llu\t%llu\t%llu\t%lf\n", base_taskname, 
		(unsigned long long)base_filecnt, (unsigned long long)base_blkcnt, 
		(unsigned long long)base_fragcnt, base_seeksim);
}


//******************************************************************************************
struct parser{
	const char *type;
	void (*start)(const char *name);
	void (*bmap)(struct bmap *bm);
	void (*report)(FILE *fp);
	void *args;
	int enabled;
};

static struct parser parsers[]= {
	{"base", base_start, base_bmap, base_report, 0},
	{"fragsize", fragsize_start, fragsize_bmap, fragsize_report, 0},
	{"fraginterval", fraginterval_start, fraginterval_bmap, fraginterval_report, 0},
	{NULL, NULL, NULL, NULL, 0}
};

int parsers_enable(const char *type)
{
	assert(!!type);
	struct parser *p;
	for(p=&parsers[0];!!p->type; p++){
		if(!strcmp(type, p->type)){
			p->enabled = 1;
			return 1;
		}
	}
	return 0;
}
void parsers_start(const char *name)
{	
	assert(!!name);
	struct parser *p;
	for(p=&parsers[0];!!p->type; p++){
		if(p->enabled){
			p->start(name);
		}
	}
}
void parsers_bmap(struct bmap *bm)
{
	assert(!!bm);
	struct parser *p;
	for(p=&parsers[0];!!p->type; p++){
		if(p->enabled){
			p->bmap(bm);
		}
	}

}
void parsers_report()
{
	struct parser *p;
	for(p=&parsers[0];!!p->type; p++){
		if(p->enabled){
			p->report(stdout);
		}
	}
}



//*****************************************************************************************



